Introduction
Welcome to the my final STAT 228 project! This time, I’ll be analyzing my own listening history data from Spotify. One of my favotrite aspects of Spotify is its ability to make song suggestions and generate playlists based on your listening history. I love the playlists it generates for me and I look forward to Spotify Wrapped every year!
In this project, I’ll spend some time looking at my listening history itself. Then, in keeping with Spotify’s most iconic feature, I’ll build a model and predict some aspects of my listening history!
For this project, I’ll be using the spotifyr package from TidyTuesday. To access my own data, I followed an incredibly helpful tutorial from Charlie Thompson who also contributed towards the spotifyr package.
Let’s get started!
Setting up Spotify
First, I had to set up a Dev account with Spotify to access their Web API, giving me access to the IDs needed to pull my access token and, in doing so, my Spotify data. More detailed instructions for this are on Charlie Thompson’s tutorial. Once the access token is pulled, every subsequent Spotify function will refer to it; no need to call it every time.
The code should look like this:
Sys.setenv(SPOTIFY_CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxx')
Sys.setenv(SPOTIFY_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxx')
access_token <- get_spotify_access_token()
Taking a peek at my data
Now that my Spotify is all set up, let’s see who my favorite artists are.
get_my_top_artists_or_tracks(type = 'artists',
time_range = 'long_term', limit = 5) %>%
select(name, genres) %>%
rowwise %>%
mutate(genres = paste(genres, collapse = ', ')) %>%
ungroup
## # A tibble: 5 × 2
## name genres
## <chr> <chr>
## 1 David Bowie art rock, classic rock, glam rock, permanent wave, rock
## 2 Amy Winehouse british soul, indie r&b, neo soul
## 3 Gorillaz alternative hip hop
## 4 Ariana Grande dance pop, pop
## 5 Talking Heads art punk, art rock, dance rock, funk rock, new wave, permanent …
Ok, this is pretty cool. Let’s look at my top songs now!
get_my_top_artists_or_tracks(type = 'tracks', time_range = 'long_term', limit = 5) %>%
mutate(artist.name = map_chr(artists, function(x) x$name[1])) %>%
select(name, artist.name, album.name)
## name artist.name
## 1 Amy Amy Amy Amy Winehouse
## 2 Doing Yoga Kazy Lambist
## 3 Toi Et Moi Paradis
## 4 Station to Station - 2016 Remaster David Bowie
## 5 Fed Up Stephen Marley
## album.name
## 1 Frank
## 2 On You (Radio Edit) - Single
## 3 Recto Verso
## 4 Station to Station (2016 Remaster)
## 5 Mind Control
As much as I love all five of these songs, I’m a little surprised by them. Well, the data doesn’t lie! Since Amy is one of my top artists and is at the very top of my most-played songs, let’s start with her.
Comparing my favorite artists
Amy Winehouse
First, I’ll create the winehouse dataset that includes all information that Spotify has about her music. Next, let’s look at valence, a Spotify statistic for measuring the joy of a song; higher valence = more joyful.
winehouse <- get_artist_audio_features('Amy Winehouse')
winehouse %>%
arrange(-valence) %>%
select(track_name, valence) %>%
head(5)
## track_name valence
## 1 Monkey Man - Live On Jools Holland Hootenanny / 2006 0.926
## 2 Monkey Man 0.923
## 3 You're Wondering Now 0.917
## 4 Tenderly - Live On Later... With Jools Holland / 2006 0.886
## 5 Stronger Than Me 0.855
Now, let’s take a look at valence for each of her albums.
winehouse %>%
filter(album_name %in% c("Frank", "Back To Black", "Lioness: Hidden Treasures", "AMY (Original Motion Picture Soundtrack)", "At The BBC")) %>%
ggplot(aes(x = valence, y = fct_reorder(album_name, album_release_year))) +
geom_density_ridges(color = "palevioletred3", fill = "palevioletred2") +
labs(title = "Joyplot of Amy Winehouse's joy distributions", subtitle = "Based on each album's valence",
x = "Valence (joy)", y = "") +
theme_minimal()

This is interesting — valence is simlar for most albums but AMY, the motion picture soundtrack. Having seen the movie, this makes a lot of sense. Amy Winehouse died in 2011, shortly after Lioness: Hidden Treasures was released. AMY is a documentary about her life, success, and tragic downfall. It makes sense that the music selected for the film would, therefore, be a lot less joyful.
David Bowie
Valence
David Bowie, my top-listened-to artist for years, can’t escape my data analysis! Let’s do the same thing, comparing the valence of his studio albums.
bowie <- get_artist_audio_features('david bowie')
bowie %>%
ggplot(aes(x = valence, y = fct_reorder(album_name, album_release_year))) +
geom_density_ridges() +
labs(title = "Joyplot of David Bowie's joy distributions", subtitle = "Based on each album's valence",
x = "Valence (joy)", y = "") +
theme_minimal()

Woah… that’s a lot of albums. To be fair, David Bowie was an extremely prolific artist; Amy Winehouse only had 5 albums on her Spotify but also died very young at 27. David Bowie’s career spanned 6 decades, not including posthumous releases.
Let’s modify this graph slightly to reflect year rather than album name. The album names themselves don’t add much to this visualization. I think it would be more interesting to simplify and emphasize the change in valence over time.
bowie %>%
mutate(decade = floor(album_release_year/10) * 10) %>%
group_by(decade) %>%
#summarize(valence_by_decade = mean(valence)) %>%
ggplot(aes(x = valence, y = factor(decade))) +
geom_density_ridges(aes(fill = decade, color = decade)) +
labs(title = "Joyplot of David Bowie's joy distributions", subtitle = "Based on each decade's valence",
x = "Valence (joy)", y = "") +
scale_y_discrete(labels = c("1960s", "1970s", "1980s", "1990s", "2000s", "2010s", "2020s")) +
theme_minimal() +
theme(legend.position = "none")

Now this looks much better! Because of the sheer number of albums David Bowie has produced, plotting valence by decade makes a lot of sense. From the visualization above, it looks like his career has remained fairly constant. Valence (joyfulness) of his music started higher and slowly dipped, looking lowest in the 80s before coming back up a little. That’s surprising since the 80s were full of upbeat, danceable music, including Bowie’s Let’s Dance album from 1983.
Winehouse VS Bowie
Before we move on to modeling, it’s probably worthwhile to compare the two musicians we’ve looked at so far. Let’s compare their overall valence.
ggplot() +
geom_density(data = bowie, aes(x = valence), color = "slateblue", fill = "slateblue", alpha = 0.5) +
geom_density(data = winehouse, aes(x = valence), color = "royalblue", fill = "royalblue", alpha = 0.5) +
labs(title = "Amy Winehouse VS David Bowie's Music Valence", x = "Valence (joy)", y = "") +
annotate("text", x = 0.68, y = 1.95, size = 4,
label = "Amy Winehouse", color = "royalblue") +
annotate("text", x = 0.39, y = 1.75, size = 4,
label = "David Bowie", color = "slateblue") +
theme_minimal() +
theme(axis.ticks.y = element_blank(), axis.text.y = element_blank())

Amy Winehouse has two distinctive peaks while David Bowie has one. I’m guessing that AMY definitely contributed to the height of that lower peak. Regardless, Amy Winehouse’s music seems to be a little more joyful than David Bowie’s.
Modeling
So far, we’ve looked a lot at valence, Spotify’s measure of a song’s joyfulness. The focus has primarily been on Amy Winehouse and David Bowie, my two top listened-to artists.
I wonder if we can build a model that predicts whether a song belongs to David Bowie or Amy Winehouse based on it’s spotify statistics: valence, danceability, speechiness,?
Creating our new dataset
First, let’s get the dataset we’ll be working with by using rbind() to add the bowie data to the winehouse data. We also have to factor artist_name in order for the classification models to work correctly.
bwjoined = rbind(winehouse, bowie)
bwjoined = bwjoined %>%
mutate(artist_name = factor(artist_name))
Now that we have both of their music data in one dataset, let’s start making our models!
Descision Tree
We’re going to build a decision tree by using Spotify’s statistics to predict the artist. The features we’ll be using are:
danceability
energy
loudness
speechiness
acousticness
instrumentalness
liveness
tempo
and, of course, valence!
Now it’s time to create the model. First, we set up by splitting the data:
# 1 Data splitting
set.seed(18) # it's a good number!
bw_split = initial_split(bwjoined, prop = 0.7)
bw_train = training(bw_split)
bw_test = testing(bw_split)
Now, we fit the model to a classification decision tree…
# 2 Fit the model
bw_tree =
decision_tree() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)
…and generate predictions…
# 3 Generate predictions
bw_pred = bw_tree %>%
predict(new_data = bw_test)
…and finally, generate prediction metrics.
# 4 Prediction metrics
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.912
The accuracy of this model is 0.91 which is very high. Because the accuracy is so high, it might be worth looking at a confusion matrix to see how the predicitons are laid out.
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Amy Winehouse David Bowie
## Amy Winehouse 57 14
## David Bowie 29 389
According to this confusion matrix, David Bowie is predicted correctly most of the time, but Amy Winehouse has a lower accuracy. It’s not extreme as she’s still predicted correctly the majority of the time, but it’s noticeable. This might be because David Bowie has many more songs than Amy Winehouse so the model is more inclined to guess Bowie from the start. If 80% of the dataset consists of Bowie’s music, even if the model guessed David Bowie every single time, it would still have 80% accuracy.
Let’s see if any of these other models can improve upon this issue by learning from their mistakes (literally!).
Bagged Decision Tree
A bagged decision tree is a model that builds many trees on different bootstrap samples of the training data, which hopefully lowers the model’s variance and keeps bias low.
# 1 Data splitting
set.seed(18) # it's a good number!
bw_split = initial_split(bwjoined, prop = 0.7)
bw_train = training(bw_split)
bw_test = testing(bw_split)
# 2 Fit the model
bw_bag_tree =
bag_tree() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)
# 3 Generate predictions
bw_pred = bw_bag_tree %>%
predict(new_data = bw_test)
# 4 Prediction metrics
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.933
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Amy Winehouse David Bowie
## Amy Winehouse 58 5
## David Bowie 28 398
In this bagged decision tree, the accuracy is now 0.93 but not much has changed. If anything, guess accuracy for David Bowie is higher but Amy Winehouse’s remains the same.
Let’s try a random forest next. Similar to a bagged decision tree, random forests use a random subset of features at each split in the tree.
Random Forest
# 1 Data splitting
set.seed(18) # it's a good number!
bw_split = initial_split(bwjoined, prop = 0.7)
bw_train = training(bw_split)
bw_test = testing(bw_split)
# 2 Fit the model
bw_rf =
rand_forest() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)
# 3 Generate predictions
bw_pred = bw_rf %>%
predict(new_data = bw_test)
# 4 Prediction metrics
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.933
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Amy Winehouse David Bowie
## Amy Winehouse 56 3
## David Bowie 30 400
With a similar outcome as the bagged tree, random forests produce a very similar output.
Gradient Boosting Model (GBM)
Gradient Boosting Models (GBMs) have become a very popular model. This is because they begin with a very simple tree and learn from the mistakes of the previous tree in the ensemble (previously generated tree in the GBM). They have options that we can tweak but we’ll keep it simple.
# 1 Data splitting
set.seed(18) # it's a good number!
bw_split = initial_split(bwjoined, prop = 0.7)
bw_train = training(bw_split)
bw_test = testing(bw_split)
# 2 Fit the model
bw_boost =
boost_tree() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bw_train)
# 3 Generate predictions
bw_pred = bw_boost %>%
predict(new_data = bw_test)
# 4 Prediction metrics
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.937
bw_test %>%
mutate(artist_pred = bw_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Amy Winehouse David Bowie
## Amy Winehouse 57 2
## David Bowie 29 401
This model definitely has the highest accuracy at 0.94. This model is fantastic at predicting David Bowie’s music but not much change has happened with Amy Winehouse’s music. Again, I attribute part of this to the difference in sample sizes but her music could just be more difficult to predict.
Bonus: Adding More Artists!
Just for fun, let’s use a GBM again to predict which artist is which… but try it across all five of my top artists!
First, to set up the dataset:
# Get the data from my other three top artists
gorillaz <- get_artist_audio_features('Gorillaz')
grande <- get_artist_audio_features('Ariana Grande')
theads <- get_artist_audio_features('Talking Heads')
# Combine all of the artists' data
all_artists = rbind(winehouse, bowie, gorillaz, grande, theads)
# Factor artist_name to use it for our GBM!
all_artists = all_artists %>%
mutate(artist_name = factor(artist_name))
Now that the data is all set, we can fit our GBM!
# 1 Data splitting
set.seed(18) # it's a good number!
all_split = initial_split(all_artists, prop = 0.7)
all_train = training(all_split)
all_test = testing(all_split)
# 2 Fit the model
all_boost =
boost_tree() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = all_train)
# 3 Generate predictions
all_pred = all_boost %>%
predict(new_data = all_test)
# 4 Prediction metrics
all_test %>%
mutate(artist_pred = all_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy multiclass 0.839
all_test %>%
mutate(artist_pred = all_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Amy Winehouse Ariana Grande David Bowie Gorillaz Talking Heads
## Amy Winehouse 56 2 5 3 0
## Ariana Grande 6 126 7 1 2
## David Bowie 18 1 372 23 27
## Gorillaz 3 2 7 55 1
## Talking Heads 3 0 11 9 72
The accuracy is 0.84. That’s not all that bad, actually! In this case, since we have so many more targets (artists), the confusion matrix suits it name — it’s pretty confusing. Still, I can see that Ariana Grande and David Bowie have the highest accuracy. In one more test, I wonder how a model would do if it just had to look at those two musicians?
Bonus II: I Can’t Stop Modeling!
Let’s just do this all in one go:
# Combine all of the artists' data
bowie_grande = rbind(bowie, grande)
# Factor artist_name to use it for our GBM!
bowie_grande = bowie_grande %>%
mutate(artist_name = factor(artist_name))
# 1 Data splitting
set.seed(18) # it's a good number!
bg_split = initial_split(bowie_grande, prop = 0.7)
bg_train = training(bg_split)
bg_test = testing(bg_split)
# 2 Fit the model
bg_boost =
boost_tree() %>%
set_mode(mode = "classification") %>%
fit(artist_name ~ danceability + energy + loudness + speechiness + acousticness + instrumentalness + liveness + tempo + valence, data = bg_train)
# 3 Generate predictions
bg_pred = bg_boost %>%
predict(new_data = bg_test)
# 4 Prediction metrics
bg_test %>%
mutate(artist_pred = bg_pred$.pred_class) %>%
accuracy(estimate = artist_pred, truth = artist_name)
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.983
bg_test %>%
mutate(artist_pred = bg_pred$.pred_class) %>%
conf_mat(estimate = artist_pred, truth = artist_name)
## Truth
## Prediction Ariana Grande David Bowie
## Ariana Grande 123 3
## David Bowie 6 407
0.98 accuracy!!! That’s darn good, especially since the confusion matrix isn’t sketchy about it!
LS0tCnRpdGxlOiAiV2UgQ291bGQgQmUgRGF0YSBTY2llbnRpc3RzLCBKdXN0IEZvciBPbmUgRGF5Li4uIgphdXRob3I6ICJLaWtpIFJlZ2FuIgpkYXRlOiAiTWF5IDEyLCAyMDIyIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogZmxhdGx5CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeW1vZGVscykKbGlicmFyeShiYWd1ZXR0ZSkKbGlicmFyeShkcGx5cikKbGlicmFyeShzcG90aWZ5cikKbGlicmFyeShnZW5pdXMpCmxpYnJhcnkoZ2dyaWRnZXMpCmxpYnJhcnkoZ2d0aGVtZXMpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCldlbGNvbWUgdG8gdGhlIG15IGZpbmFsIFNUQVQgMjI4IHByb2plY3QhIFRoaXMgdGltZSwgSSdsbCBiZSBhbmFseXppbmcgKm15IG93biogbGlzdGVuaW5nIGhpc3RvcnkgZGF0YSBmcm9tIFNwb3RpZnkuIE9uZSBvZiBteSBmYXZvdHJpdGUgYXNwZWN0cyBvZiBTcG90aWZ5IGlzIGl0cyBhYmlsaXR5IHRvIG1ha2Ugc29uZyBzdWdnZXN0aW9ucyBhbmQgZ2VuZXJhdGUgcGxheWxpc3RzIGJhc2VkIG9uIHlvdXIgbGlzdGVuaW5nIGhpc3RvcnkuIEkgbG92ZSB0aGUgcGxheWxpc3RzIGl0IGdlbmVyYXRlcyBmb3IgbWUgYW5kIEkgbG9vayBmb3J3YXJkIHRvIFNwb3RpZnkgV3JhcHBlZCBldmVyeSB5ZWFyISAKCkluIHRoaXMgcHJvamVjdCwgSSdsbCBzcGVuZCBzb21lIHRpbWUgbG9va2luZyBhdCBteSBsaXN0ZW5pbmcgaGlzdG9yeSBpdHNlbGYuIFRoZW4sIGluIGtlZXBpbmcgd2l0aCBTcG90aWZ5J3MgbW9zdCBpY29uaWMgZmVhdHVyZSwgSSdsbCBidWlsZCBhIG1vZGVsIGFuZCAqKnByZWRpY3QqKiBzb21lIGFzcGVjdHMgb2YgbXkgbGlzdGVuaW5nIGhpc3RvcnkhCgpGb3IgdGhpcyBwcm9qZWN0LCBJJ2xsIGJlIHVzaW5nIHRoZSBgc3BvdGlmeXJgIHBhY2thZ2UgZnJvbSBbVGlkeVR1ZXNkYXldKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvYmxvYi9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDEtMjEvcmVhZG1lLm1kKS4gVG8gYWNjZXNzIG15IG93biBkYXRhLCBJIGZvbGxvd2VkIGFuIGluY3JlZGlibHkgaGVscGZ1bCB0dXRvcmlhbCBmcm9tIFtDaGFybGllIFRob21wc29uXShodHRwczovL3d3dy5yY2hhcmxpZS5jb20vc3BvdGlmeXIvKSB3aG8gYWxzbyBjb250cmlidXRlZCB0b3dhcmRzIHRoZSBgc3BvdGlmeXJgIHBhY2thZ2UuCgpMZXQncyBnZXQgc3RhcnRlZCEKCiMjIFNldHRpbmcgdXAgU3BvdGlmeQoKIVsqTXkgZmFjZSB3aGVuIEkgZm91bmQgb3V0IHlvdSBjb3VsZCBhY2Nlc3MgeW91ciBvd24gU3BvdGlmeSBkYXRhKl0oaHR0cDovL3d3dy5yZWFjdGlvbmdpZnMuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE0LzAxL1Nob2NrZWQtRGF2aWQtQm93aWUuZ2lmKQoKRmlyc3QsIEkgaGFkIHRvIHNldCB1cCBhIERldiBhY2NvdW50IHdpdGggU3BvdGlmeSB0byBhY2Nlc3MgdGhlaXIgV2ViIEFQSSwgZ2l2aW5nIG1lIGFjY2VzcyB0byB0aGUgSURzIG5lZWRlZCB0byBwdWxsIG15IGFjY2VzcyB0b2tlbiBhbmQsIGluIGRvaW5nIHNvLCBteSBTcG90aWZ5IGRhdGEuIE1vcmUgZGV0YWlsZWQgaW5zdHJ1Y3Rpb25zIGZvciB0aGlzIGFyZSBvbiBbQ2hhcmxpZSBUaG9tcHNvbidzIHR1dG9yaWFsXShodHRwczovL3d3dy5yY2hhcmxpZS5jb20vc3BvdGlmeXIvKS4gT25jZSB0aGUgYWNjZXNzIHRva2VuIGlzIHB1bGxlZCwgZXZlcnkgc3Vic2VxdWVudCBTcG90aWZ5IGZ1bmN0aW9uIHdpbGwgcmVmZXIgdG8gaXQ7IG5vIG5lZWQgdG8gY2FsbCBpdCBldmVyeSB0aW1lLgoKVGhlIGNvZGUgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnNwb3RpZnlfc29uZ3MgPC0gcmVhZHI6OnJlYWRfY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMS0yMS9zcG90aWZ5X3NvbmdzLmNzdicpCgojVmlldyhzcG90aWZ5X3NvbmdzKQpgYGAKYGBge3IsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9ClN5cy5zZXRlbnYoU1BPVElGWV9DTElFTlRfSUQgPSAnMDA2ZDk3MzQ1NDU3NDU2ZmI1OTk5MTg1NTk1N2QyNjgnKQpTeXMuc2V0ZW52KFNQT1RJRllfQ0xJRU5UX1NFQ1JFVCA9ICc4MGYzYzY5ZGU5N2M0YTFhOTY3NzM1YjhkYTQ5ODk2MycpCgphY2Nlc3NfdG9rZW4gPC0gZ2V0X3Nwb3RpZnlfYWNjZXNzX3Rva2VuKCkKYGBgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KU3lzLnNldGVudihTUE9USUZZX0NMSUVOVF9JRCA9ICd4eHh4eHh4eHh4eHh4eHh4eHh4eHgnKQpTeXMuc2V0ZW52KFNQT1RJRllfQ0xJRU5UX1NFQ1JFVCA9ICd4eHh4eHh4eHh4eHh4eHh4eHh4eHgnKQoKYWNjZXNzX3Rva2VuIDwtIGdldF9zcG90aWZ5X2FjY2Vzc190b2tlbigpCmBgYAoKIyMgVGFraW5nIGEgcGVlayBhdCBteSBkYXRhCgpOb3cgdGhhdCBteSBTcG90aWZ5IGlzIGFsbCBzZXQgdXAsIGxldCdzIHNlZSB3aG8gbXkgZmF2b3JpdGUgYXJ0aXN0cyBhcmUuIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpnZXRfbXlfdG9wX2FydGlzdHNfb3JfdHJhY2tzKHR5cGUgPSAnYXJ0aXN0cycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVfcmFuZ2UgPSAnbG9uZ190ZXJtJywgbGltaXQgPSA1KSAlPiUgCiAgICBzZWxlY3QobmFtZSwgZ2VucmVzKSAlPiUgCiAgICByb3d3aXNlICU+JSAKICAgIG11dGF0ZShnZW5yZXMgPSBwYXN0ZShnZW5yZXMsIGNvbGxhcHNlID0gJywgJykpICU+JSAKICAgIHVuZ3JvdXAgCmBgYAoKT2ssIHRoaXMgaXMgcHJldHR5IGNvb2wuIExldCdzIGxvb2sgYXQgbXkgdG9wIHNvbmdzIG5vdyEKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KZ2V0X215X3RvcF9hcnRpc3RzX29yX3RyYWNrcyh0eXBlID0gJ3RyYWNrcycsIHRpbWVfcmFuZ2UgPSAnbG9uZ190ZXJtJywgbGltaXQgPSA1KSAlPiUgCiAgICBtdXRhdGUoYXJ0aXN0Lm5hbWUgPSBtYXBfY2hyKGFydGlzdHMsIGZ1bmN0aW9uKHgpIHgkbmFtZVsxXSkpICU+JSAKICAgIHNlbGVjdChuYW1lLCBhcnRpc3QubmFtZSwgYWxidW0ubmFtZSkKYGBgCgpBcyBtdWNoIGFzIEkgbG92ZSBhbGwgZml2ZSBvZiB0aGVzZSBzb25ncywgSSdtIGEgbGl0dGxlIHN1cnByaXNlZCBieSB0aGVtLiBXZWxsLCB0aGUgZGF0YSBkb2Vzbid0IGxpZSEgU2luY2UgQW15IGlzIG9uZSBvZiBteSB0b3AgYXJ0aXN0cyBhbmQgaXMgYXQgdGhlIHZlcnkgdG9wIG9mIG15IG1vc3QtcGxheWVkIHNvbmdzLCBsZXQncyBzdGFydCB3aXRoIGhlci4KCiMgQ29tcGFyaW5nIG15IGZhdm9yaXRlIGFydGlzdHMKCiMjIEFteSBXaW5laG91c2UKCkZpcnN0LCBJJ2xsIGNyZWF0ZSB0aGUgYHdpbmVob3VzZWAgZGF0YXNldCB0aGF0IGluY2x1ZGVzIGFsbCBpbmZvcm1hdGlvbiB0aGF0IFNwb3RpZnkgaGFzIGFib3V0IGhlciBtdXNpYy4gTmV4dCwgbGV0J3MgbG9vayBhdCAqKnZhbGVuY2UqKiwgYSBTcG90aWZ5IHN0YXRpc3RpYyBmb3IgbWVhc3VyaW5nIHRoZSAqKmpveSoqIG9mIGEgc29uZzsgaGlnaGVyIHZhbGVuY2UgPSBtb3JlIGpveWZ1bC4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kd2luZWhvdXNlIDwtIGdldF9hcnRpc3RfYXVkaW9fZmVhdHVyZXMoJ0FteSBXaW5laG91c2UnKQp3aW5laG91c2UgJT4lIAogICAgYXJyYW5nZSgtdmFsZW5jZSkgJT4lIAogICAgc2VsZWN0KHRyYWNrX25hbWUsIHZhbGVuY2UpICU+JSAKICAgIGhlYWQoNSkgCmBgYAoKTm93LCBsZXQncyB0YWtlIGEgbG9vayBhdCB2YWxlbmNlIGZvciBlYWNoIG9mIGhlciBhbGJ1bXMuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CndpbmVob3VzZSAlPiUgCiAgZmlsdGVyKGFsYnVtX25hbWUgJWluJSBjKCJGcmFuayIsICJCYWNrIFRvIEJsYWNrIiwgIkxpb25lc3M6IEhpZGRlbiBUcmVhc3VyZXMiLCAiQU1ZIChPcmlnaW5hbCBNb3Rpb24gUGljdHVyZSBTb3VuZHRyYWNrKSIsICJBdCBUaGUgQkJDIikpICU+JSAKZ2dwbG90KGFlcyh4ID0gdmFsZW5jZSwgeSA9IGZjdF9yZW9yZGVyKGFsYnVtX25hbWUsIGFsYnVtX3JlbGVhc2VfeWVhcikpKSArIAogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhjb2xvciA9ICJwYWxldmlvbGV0cmVkMyIsIGZpbGwgPSAicGFsZXZpb2xldHJlZDIiKSArIAogICAgbGFicyh0aXRsZSA9ICJKb3lwbG90IG9mIEFteSBXaW5laG91c2UncyBqb3kgZGlzdHJpYnV0aW9ucyIsIHN1YnRpdGxlID0gIkJhc2VkIG9uIGVhY2ggYWxidW0ncyB2YWxlbmNlIiwgCiAgICAgICAgICAgIHggPSAiVmFsZW5jZSAoam95KSIsIHkgPSAiIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClRoaXMgaXMgaW50ZXJlc3Rpbmcg4oCUIHZhbGVuY2UgaXMgc2ltbGFyIGZvciBtb3N0IGFsYnVtcyBidXQgKkFNWSosIHRoZSBtb3Rpb24gcGljdHVyZSBzb3VuZHRyYWNrLiBIYXZpbmcgc2VlbiB0aGUgbW92aWUsIHRoaXMgbWFrZXMgYSBsb3Qgb2Ygc2Vuc2UuIEFteSBXaW5laG91c2UgZGllZCBpbiAyMDExLCBzaG9ydGx5IGFmdGVyICpMaW9uZXNzOiBIaWRkZW4gVHJlYXN1cmVzKiB3YXMgcmVsZWFzZWQuICpBTVkqIGlzIGEgZG9jdW1lbnRhcnkgYWJvdXQgaGVyIGxpZmUsIHN1Y2Nlc3MsIGFuZCB0cmFnaWMgZG93bmZhbGwuIEl0IG1ha2VzIHNlbnNlIHRoYXQgdGhlIG11c2ljIHNlbGVjdGVkIGZvciB0aGUgZmlsbSB3b3VsZCwgdGhlcmVmb3JlLCBiZSBhIGxvdCBsZXNzIGpveWZ1bC4KCiMjIERhdmlkIEJvd2llCgojIyMgVmFsZW5jZQoKRGF2aWQgQm93aWUsIG15IHRvcC1saXN0ZW5lZC10byBhcnRpc3QgZm9yIHllYXJzLCBjYW4ndCBlc2NhcGUgbXkgZGF0YSBhbmFseXNpcyEgTGV0J3MgZG8gdGhlIHNhbWUgdGhpbmcsIGNvbXBhcmluZyB0aGUgdmFsZW5jZSBvZiBoaXMgc3R1ZGlvIGFsYnVtcy4gCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmJvd2llIDwtIGdldF9hcnRpc3RfYXVkaW9fZmVhdHVyZXMoJ2RhdmlkIGJvd2llJykKCmJvd2llICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YWxlbmNlLCB5ID0gZmN0X3Jlb3JkZXIoYWxidW1fbmFtZSwgYWxidW1fcmVsZWFzZV95ZWFyKSkpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKyAKICAgIGxhYnModGl0bGUgPSAiSm95cGxvdCBvZiBEYXZpZCBCb3dpZSdzIGpveSBkaXN0cmlidXRpb25zIiwgc3VidGl0bGUgPSAiQmFzZWQgb24gZWFjaCBhbGJ1bSdzIHZhbGVuY2UiLCAKICAgICAgICAgICAgeCA9ICJWYWxlbmNlIChqb3kpIiwgeSA9ICIiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKV29haC4uLiB0aGF0J3MgYSAqbG90KiBvZiBhbGJ1bXMuIFRvIGJlIGZhaXIsIERhdmlkIEJvd2llIHdhcyBhbiAqZXh0cmVtZWx5KiBwcm9saWZpYyBhcnRpc3Q7IEFteSBXaW5laG91c2Ugb25seSBoYWQgNSBhbGJ1bXMgb24gaGVyIFNwb3RpZnkgYnV0IGFsc28gZGllZCB2ZXJ5IHlvdW5nIGF0IDI3LiBEYXZpZCBCb3dpZSdzIGNhcmVlciBzcGFubmVkIDYgZGVjYWRlcywgbm90IGluY2x1ZGluZyBwb3N0aHVtb3VzIHJlbGVhc2VzLiAKCkxldCdzIG1vZGlmeSB0aGlzIGdyYXBoICpzbGlnaHRseSogdG8gcmVmbGVjdCB5ZWFyIHJhdGhlciB0aGFuIGFsYnVtIG5hbWUuIFRoZSBhbGJ1bSBuYW1lcyB0aGVtc2VsdmVzIGRvbid0IGFkZCBtdWNoIHRvIHRoaXMgdmlzdWFsaXphdGlvbi4gSSB0aGluayBpdCB3b3VsZCBiZSBtb3JlIGludGVyZXN0aW5nIHRvIHNpbXBsaWZ5IGFuZCBlbXBoYXNpemUgdGhlIGNoYW5nZSBpbiB2YWxlbmNlIG92ZXIgdGltZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KYm93aWUgJT4lIAogIG11dGF0ZShkZWNhZGUgPSBmbG9vcihhbGJ1bV9yZWxlYXNlX3llYXIvMTApICogMTApICU+JSAKICBncm91cF9ieShkZWNhZGUpICU+JSAKICAjc3VtbWFyaXplKHZhbGVuY2VfYnlfZGVjYWRlID0gbWVhbih2YWxlbmNlKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbGVuY2UsIHkgPSBmYWN0b3IoZGVjYWRlKSkpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gZGVjYWRlLCBjb2xvciA9IGRlY2FkZSkpICsgCiAgICBsYWJzKHRpdGxlID0gIkpveXBsb3Qgb2YgRGF2aWQgQm93aWUncyBqb3kgZGlzdHJpYnV0aW9ucyIsIHN1YnRpdGxlID0gIkJhc2VkIG9uIGVhY2ggZGVjYWRlJ3MgdmFsZW5jZSIsIAogICAgICAgICAgICB4ID0gIlZhbGVuY2UgKGpveSkiLCB5ID0gIiIpICsKICBzY2FsZV95X2Rpc2NyZXRlKGxhYmVscyA9IGMoIjE5NjBzIiwgIjE5NzBzIiwgIjE5ODBzIiwgIjE5OTBzIiwgIjIwMDBzIiwgIjIwMTBzIiwgIjIwMjBzIikpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpOb3cgdGhpcyBsb29rcyBtdWNoIGJldHRlciEgQmVjYXVzZSBvZiB0aGUgc2hlZXIgbnVtYmVyIG9mIGFsYnVtcyBEYXZpZCBCb3dpZSBoYXMgcHJvZHVjZWQsIHBsb3R0aW5nIHZhbGVuY2UgYnkgZGVjYWRlIG1ha2VzIGEgbG90IG9mIHNlbnNlLiBGcm9tIHRoZSB2aXN1YWxpemF0aW9uIGFib3ZlLCBpdCBsb29rcyBsaWtlIGhpcyBjYXJlZXIgaGFzIHJlbWFpbmVkIGZhaXJseSBjb25zdGFudC4gVmFsZW5jZSAoam95ZnVsbmVzcykgb2YgaGlzIG11c2ljIHN0YXJ0ZWQgaGlnaGVyIGFuZCBzbG93bHkgZGlwcGVkLCBsb29raW5nIGxvd2VzdCBpbiB0aGUgODBzIGJlZm9yZSBjb21pbmcgYmFjayB1cCBhIGxpdHRsZS4gVGhhdCdzIHN1cnByaXNpbmcgc2luY2UgdGhlIDgwcyB3ZXJlIGZ1bGwgb2YgdXBiZWF0LCBkYW5jZWFibGUgbXVzaWMsIGluY2x1ZGluZyBCb3dpZSdzICpMZXQncyBEYW5jZSogYWxidW0gZnJvbSAxOTgzLiAKCiMgV2luZWhvdXNlIFZTIEJvd2llCgpCZWZvcmUgd2UgbW92ZSBvbiB0byBtb2RlbGluZywgaXQncyBwcm9iYWJseSB3b3J0aHdoaWxlIHRvIGNvbXBhcmUgdGhlIHR3byBtdXNpY2lhbnMgd2UndmUgbG9va2VkIGF0IHNvIGZhci4gTGV0J3MgY29tcGFyZSB0aGVpciBvdmVyYWxsICoqdmFsZW5jZSoqLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpnZ3Bsb3QoKSArIAogIGdlb21fZGVuc2l0eShkYXRhID0gYm93aWUsIGFlcyh4ID0gdmFsZW5jZSksIGNvbG9yID0gInNsYXRlYmx1ZSIsIGZpbGwgPSAic2xhdGVibHVlIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX2RlbnNpdHkoZGF0YSA9IHdpbmVob3VzZSwgYWVzKHggPSB2YWxlbmNlKSwgY29sb3IgPSAicm95YWxibHVlIiwgZmlsbCA9ICJyb3lhbGJsdWUiLCBhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiQW15IFdpbmVob3VzZSBWUyBEYXZpZCBCb3dpZSdzIE11c2ljIFZhbGVuY2UiLCB4ID0gIlZhbGVuY2UgKGpveSkiLCB5ID0gIiIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjY4LCB5ID0gMS45NSwgc2l6ZSA9IDQsCiAgICAgICAgICAgbGFiZWwgPSAiQW15IFdpbmVob3VzZSIsIGNvbG9yID0gInJveWFsYmx1ZSIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjM5LCB5ID0gMS43NSwgc2l6ZSA9IDQsCiAgICAgICAgICAgbGFiZWwgPSAiRGF2aWQgQm93aWUiLCBjb2xvciA9ICJzbGF0ZWJsdWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkFteSBXaW5laG91c2UgaGFzIHR3byBkaXN0aW5jdGl2ZSBwZWFrcyB3aGlsZSBEYXZpZCBCb3dpZSBoYXMgb25lLiBJJ20gZ3Vlc3NpbmcgdGhhdCAqQU1ZKiBkZWZpbml0ZWx5IGNvbnRyaWJ1dGVkIHRvIHRoZSBoZWlnaHQgb2YgdGhhdCBsb3dlciBwZWFrLiBSZWdhcmRsZXNzLCBBbXkgV2luZWhvdXNlJ3MgbXVzaWMgc2VlbXMgdG8gYmUgYSBsaXR0bGUgbW9yZSBqb3lmdWwgdGhhbiBEYXZpZCBCb3dpZSdzLiAKCiMgTW9kZWxpbmcKClNvIGZhciwgd2UndmUgbG9va2VkIGEgbG90IGF0IHZhbGVuY2UsIFNwb3RpZnkncyBtZWFzdXJlIG9mIGEgc29uZydzIGpveWZ1bG5lc3MuIFRoZSBmb2N1cyBoYXMgcHJpbWFyaWx5IGJlZW4gb24gQW15IFdpbmVob3VzZSBhbmQgRGF2aWQgQm93aWUsIG15IHR3byB0b3AgbGlzdGVuZWQtdG8gYXJ0aXN0cy4gCgpJIHdvbmRlciBpZiB3ZSBjYW4gYnVpbGQgYSBtb2RlbCB0aGF0IHByZWRpY3RzIHdoZXRoZXIgYSBzb25nIGJlbG9uZ3MgdG8gRGF2aWQgQm93aWUgb3IgQW15IFdpbmVob3VzZSBiYXNlZCBvbiBpdCdzIHNwb3RpZnkgc3RhdGlzdGljczogKip2YWxlbmNlLCBkYW5jZWFiaWxpdHksIHNwZWVjaGluZXNzLCoqPwoKIyMgQ3JlYXRpbmcgb3VyIG5ldyBkYXRhc2V0CgpGaXJzdCwgbGV0J3MgZ2V0IHRoZSBkYXRhc2V0IHdlJ2xsIGJlIHdvcmtpbmcgd2l0aCBieSB1c2luZyBgcmJpbmQoKWAgdG8gYWRkIHRoZSBgYm93aWVgIGRhdGEgdG8gdGhlIGB3aW5laG91c2VgIGRhdGEuIFdlIGFsc28gaGF2ZSB0byBmYWN0b3IgYGFydGlzdF9uYW1lYCBpbiBvcmRlciBmb3IgdGhlIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB0byB3b3JrIGNvcnJlY3RseS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KYndqb2luZWQgPSByYmluZCh3aW5laG91c2UsIGJvd2llKQoKYndqb2luZWQgPSBid2pvaW5lZCAlPiUgCiAgbXV0YXRlKGFydGlzdF9uYW1lID0gZmFjdG9yKGFydGlzdF9uYW1lKSkKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIGJvdGggb2YgdGhlaXIgbXVzaWMgZGF0YSBpbiBvbmUgZGF0YXNldCwgbGV0J3Mgc3RhcnQgbWFraW5nIG91ciBtb2RlbHMhCgojIyBEZXNjaXNpb24gVHJlZQoKV2UncmUgZ29pbmcgdG8gYnVpbGQgYSAqKmRlY2lzaW9uIHRyZWUqKiBieSB1c2luZyBTcG90aWZ5J3Mgc3RhdGlzdGljcyB0byBwcmVkaWN0IHRoZSBhcnRpc3QuIFRoZSBmZWF0dXJlcyB3ZSdsbCBiZSB1c2luZyBhcmU6CgotIGBkYW5jZWFiaWxpdHlgCgotIGBlbmVyZ3lgCgotIGBsb3VkbmVzc2AKCi0gYHNwZWVjaGluZXNzYAoKLSBgYWNvdXN0aWNuZXNzYAoKLSBgaW5zdHJ1bWVudGFsbmVzc2AKCi0gYGxpdmVuZXNzYAoKLSBgdGVtcG9gCgotIGFuZCwgb2YgY291cnNlLCBgdmFsZW5jZWAhCgpOb3cgaXQncyB0aW1lIHRvIGNyZWF0ZSB0aGUgbW9kZWwuIEZpcnN0LCB3ZSBzZXQgdXAgYnkgc3BsaXR0aW5nIHRoZSBkYXRhOgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIDEgRGF0YSBzcGxpdHRpbmcKc2V0LnNlZWQoMTgpICMgaXQncyBhIGdvb2QgbnVtYmVyISAKYndfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGJ3am9pbmVkLCBwcm9wID0gMC43KQoKYndfdHJhaW4gPSB0cmFpbmluZyhid19zcGxpdCkKYndfdGVzdCA9IHRlc3RpbmcoYndfc3BsaXQpCmBgYAoKTm93LCB3ZSBmaXQgdGhlIG1vZGVsIHRvIGEgY2xhc3NpZmljYXRpb24gZGVjaXNpb24gdHJlZS4uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIDIgRml0IHRoZSBtb2RlbApid190cmVlID0gCiAgZGVjaXNpb25fdHJlZSgpICU+JQogIHNldF9tb2RlKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKSAlPiUKICBmaXQoYXJ0aXN0X25hbWUgfiBkYW5jZWFiaWxpdHkgKyBlbmVyZ3kgKyBsb3VkbmVzcyArIHNwZWVjaGluZXNzICsgYWNvdXN0aWNuZXNzICsgaW5zdHJ1bWVudGFsbmVzcyArIGxpdmVuZXNzICsgdGVtcG8gKyB2YWxlbmNlLCBkYXRhID0gYndfdHJhaW4pCmBgYAoKLi4uYW5kIGdlbmVyYXRlIHByZWRpY3Rpb25zLi4uCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpid19wcmVkID0gYndfdHJlZSAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKYGBgCgouLi5hbmQgZmluYWxseSwgZ2VuZXJhdGUgcHJlZGljdGlvbiBtZXRyaWNzLiAKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyA0IFByZWRpY3Rpb24gbWV0cmljcwpid190ZXN0ICU+JQogIG11dGF0ZShhcnRpc3RfcHJlZCA9IGJ3X3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGFjY3VyYWN5KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCmBgYAoKVGhlIGFjY3VyYWN5IG9mIHRoaXMgbW9kZWwgaXMgKiowLjkxKiogd2hpY2ggaXMgKnZlcnkqIGhpZ2guIEJlY2F1c2UgdGhlIGFjY3VyYWN5IGlzIHNvIGhpZ2gsIGl0IG1pZ2h0IGJlIHdvcnRoIGxvb2tpbmcgYXQgYSAqKmNvbmZ1c2lvbiBtYXRyaXgqKiB0byBzZWUgaG93IHRoZSBwcmVkaWNpdG9ucyBhcmUgbGFpZCBvdXQuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmJ3X3Rlc3QgJT4lIAogIG11dGF0ZShhcnRpc3RfcHJlZCA9IGJ3X3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGNvbmZfbWF0KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCmBgYAoKQWNjb3JkaW5nIHRvIHRoaXMgY29uZnVzaW9uIG1hdHJpeCwgRGF2aWQgQm93aWUgaXMgcHJlZGljdGVkIGNvcnJlY3RseSBtb3N0IG9mIHRoZSB0aW1lLCBidXQgQW15IFdpbmVob3VzZSBoYXMgYSBsb3dlciBhY2N1cmFjeS4gSXQncyBub3QgZXh0cmVtZSBhcyBzaGUncyBzdGlsbCBwcmVkaWN0ZWQgY29ycmVjdGx5IHRoZSBtYWpvcml0eSBvZiB0aGUgdGltZSwgYnV0IGl0J3Mgbm90aWNlYWJsZS4gVGhpcyBtaWdodCBiZSBiZWNhdXNlIERhdmlkIEJvd2llIGhhcyBtYW55IG1vcmUgc29uZ3MgdGhhbiBBbXkgV2luZWhvdXNlIHNvIHRoZSBtb2RlbCBpcyBtb3JlIGluY2xpbmVkIHRvIGd1ZXNzIEJvd2llIGZyb20gdGhlIHN0YXJ0LiBJZiA4MCUgb2YgdGhlIGRhdGFzZXQgY29uc2lzdHMgb2YgQm93aWUncyBtdXNpYywgZXZlbiBpZiB0aGUgbW9kZWwgZ3Vlc3NlZCBEYXZpZCBCb3dpZSBldmVyeSBzaW5nbGUgdGltZSwgaXQgd291bGQgc3RpbGwgaGF2ZSA4MCUgYWNjdXJhY3kuCgpMZXQncyBzZWUgaWYgYW55IG9mIHRoZXNlIG90aGVyIG1vZGVscyBjYW4gaW1wcm92ZSB1cG9uIHRoaXMgaXNzdWUgYnkgbGVhcm5pbmcgZnJvbSB0aGVpciBtaXN0YWtlcyAobGl0ZXJhbGx5ISkuCgojIyBCYWdnZWQgRGVjaXNpb24gVHJlZQoKQSAqKmJhZ2dlZCBkZWNpc2lvbiB0cmVlKiogaXMgYSBtb2RlbCB0aGF0IGJ1aWxkcyBtYW55IHRyZWVzIG9uIGRpZmZlcmVudCBib290c3RyYXAgc2FtcGxlcyBvZiB0aGUgdHJhaW5pbmcgZGF0YSwgd2hpY2ggaG9wZWZ1bGx5IGxvd2VycyB0aGUgbW9kZWwncyB2YXJpYW5jZSBhbmQga2VlcHMgYmlhcyBsb3cuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIApid19zcGxpdCA9IGluaXRpYWxfc3BsaXQoYndqb2luZWQsIHByb3AgPSAwLjcpCgpid190cmFpbiA9IHRyYWluaW5nKGJ3X3NwbGl0KQpid190ZXN0ID0gdGVzdGluZyhid19zcGxpdCkKCiMgMiBGaXQgdGhlIG1vZGVsCmJ3X2JhZ190cmVlID0gCiAgYmFnX3RyZWUoKSAlPiUKICBzZXRfbW9kZShtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgZml0KGFydGlzdF9uYW1lIH4gZGFuY2VhYmlsaXR5ICsgZW5lcmd5ICsgbG91ZG5lc3MgKyBzcGVlY2hpbmVzcyArIGFjb3VzdGljbmVzcyArIGluc3RydW1lbnRhbG5lc3MgKyBsaXZlbmVzcyArIHRlbXBvICsgdmFsZW5jZSwgZGF0YSA9IGJ3X3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmJ3X3ByZWQgPSBid19iYWdfdHJlZSAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeShlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQoKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBjb25mX21hdChlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQpgYGAKCkluIHRoaXMgYmFnZ2VkIGRlY2lzaW9uIHRyZWUsIHRoZSBhY2N1cmFjeSBpcyBub3cgMC45MyBidXQgbm90IG11Y2ggaGFzIGNoYW5nZWQuIElmIGFueXRoaW5nLCBndWVzcyBhY2N1cmFjeSBmb3IgRGF2aWQgQm93aWUgaXMgaGlnaGVyIGJ1dCBBbXkgV2luZWhvdXNlJ3MgcmVtYWlucyB0aGUgc2FtZS4KCkxldCdzIHRyeSBhICoqcmFuZG9tIGZvcmVzdCoqIG5leHQuIFNpbWlsYXIgdG8gYSBiYWdnZWQgZGVjaXNpb24gdHJlZSwgcmFuZG9tIGZvcmVzdHMgdXNlIGEgKnJhbmRvbSBzdWJzZXQgb2YgZmVhdHVyZXMqIGF0IGVhY2ggc3BsaXQgaW4gdGhlIHRyZWUuCgojIyBSYW5kb20gRm9yZXN0CgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIApid19zcGxpdCA9IGluaXRpYWxfc3BsaXQoYndqb2luZWQsIHByb3AgPSAwLjcpCgpid190cmFpbiA9IHRyYWluaW5nKGJ3X3NwbGl0KQpid190ZXN0ID0gdGVzdGluZyhid19zcGxpdCkKCiMgMiBGaXQgdGhlIG1vZGVsCmJ3X3JmID0gCiAgcmFuZF9mb3Jlc3QoKSAlPiUKICBzZXRfbW9kZShtb2RlID0gImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgZml0KGFydGlzdF9uYW1lIH4gZGFuY2VhYmlsaXR5ICsgZW5lcmd5ICsgbG91ZG5lc3MgKyBzcGVlY2hpbmVzcyArIGFjb3VzdGljbmVzcyArIGluc3RydW1lbnRhbG5lc3MgKyBsaXZlbmVzcyArIHRlbXBvICsgdmFsZW5jZSwgZGF0YSA9IGJ3X3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmJ3X3ByZWQgPSBid19yZiAlPiUKICBwcmVkaWN0KG5ld19kYXRhID0gYndfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeShlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQoKYndfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBid19wcmVkJC5wcmVkX2NsYXNzKSAlPiUKICBjb25mX21hdChlc3RpbWF0ZSA9IGFydGlzdF9wcmVkLCB0cnV0aCA9IGFydGlzdF9uYW1lKQpgYGAKCldpdGggYSBzaW1pbGFyIG91dGNvbWUgYXMgdGhlIGJhZ2dlZCB0cmVlLCByYW5kb20gZm9yZXN0cyBwcm9kdWNlIGEgdmVyeSBzaW1pbGFyIG91dHB1dC4gCgojIyBHcmFkaWVudCBCb29zdGluZyBNb2RlbCAoR0JNKQoKKipHcmFkaWVudCBCb29zdGluZyBNb2RlbHMqKiAoR0JNcykgaGF2ZSBiZWNvbWUgYSB2ZXJ5IHBvcHVsYXIgbW9kZWwuIFRoaXMgaXMgYmVjYXVzZSB0aGV5IGJlZ2luIHdpdGggYSB2ZXJ5IHNpbXBsZSB0cmVlIGFuZCBsZWFybiBmcm9tIHRoZSBtaXN0YWtlcyBvZiB0aGUgcHJldmlvdXMgdHJlZSBpbiB0aGUgZW5zZW1ibGUgKHByZXZpb3VzbHkgZ2VuZXJhdGVkIHRyZWUgaW4gdGhlIEdCTSkuIFRoZXkgaGF2ZSBvcHRpb25zIHRoYXQgd2UgY2FuIHR3ZWFrIGJ1dCB3ZSdsbCBrZWVwIGl0IHNpbXBsZS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyAxIERhdGEgc3BsaXR0aW5nCnNldC5zZWVkKDE4KSAjIGl0J3MgYSBnb29kIG51bWJlciEgCmJ3X3NwbGl0ID0gaW5pdGlhbF9zcGxpdChid2pvaW5lZCwgcHJvcCA9IDAuNykKCmJ3X3RyYWluID0gdHJhaW5pbmcoYndfc3BsaXQpCmJ3X3Rlc3QgPSB0ZXN0aW5nKGJ3X3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYndfYm9vc3QgPSAKICBib29zdF90cmVlKCkgJT4lCiAgc2V0X21vZGUobW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIpICU+JQogIGZpdChhcnRpc3RfbmFtZSB+IGRhbmNlYWJpbGl0eSArIGVuZXJneSArIGxvdWRuZXNzICsgc3BlZWNoaW5lc3MgKyBhY291c3RpY25lc3MgKyBpbnN0cnVtZW50YWxuZXNzICsgbGl2ZW5lc3MgKyB0ZW1wbyArIHZhbGVuY2UsIGRhdGEgPSBid190cmFpbikKCiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpid19wcmVkID0gYndfYm9vc3QgJT4lCiAgcHJlZGljdChuZXdfZGF0YSA9IGJ3X3Rlc3QpCgojIDQgUHJlZGljdGlvbiBtZXRyaWNzCmJ3X3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYndfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3koZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKCmJ3X3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYndfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgpUaGlzIG1vZGVsIGRlZmluaXRlbHkgaGFzIHRoZSBoaWdoZXN0IGFjY3VyYWN5IGF0ICoqMC45NCoqLiBUaGlzIG1vZGVsIGlzIGZhbnRhc3RpYyBhdCBwcmVkaWN0aW5nIERhdmlkIEJvd2llJ3MgbXVzaWMgYnV0IG5vdCBtdWNoIGNoYW5nZSBoYXMgaGFwcGVuZWQgd2l0aCBBbXkgV2luZWhvdXNlJ3MgbXVzaWMuIEFnYWluLCBJIGF0dHJpYnV0ZSBwYXJ0IG9mIHRoaXMgdG8gdGhlIGRpZmZlcmVuY2UgaW4gc2FtcGxlIHNpemVzIGJ1dCBoZXIgbXVzaWMgY291bGQganVzdCBiZSBtb3JlIGRpZmZpY3VsdCB0byBwcmVkaWN0LiAKCiMjIEJvbnVzOiBBZGRpbmcgTW9yZSBBcnRpc3RzIQoKSnVzdCBmb3IgZnVuLCBsZXQncyB1c2UgYSAqKkdCTSoqIGFnYWluIHRvIHByZWRpY3Qgd2hpY2ggYXJ0aXN0IGlzIHdoaWNoLi4uIGJ1dCB0cnkgaXQgYWNyb3NzICphbGwgZml2ZSogb2YgbXkgdG9wIGFydGlzdHMhCgpGaXJzdCwgdG8gc2V0IHVwIHRoZSBkYXRhc2V0OgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgR2V0IHRoZSBkYXRhIGZyb20gbXkgb3RoZXIgdGhyZWUgdG9wIGFydGlzdHMKZ29yaWxsYXogPC0gZ2V0X2FydGlzdF9hdWRpb19mZWF0dXJlcygnR29yaWxsYXonKQpncmFuZGUgPC0gZ2V0X2FydGlzdF9hdWRpb19mZWF0dXJlcygnQXJpYW5hIEdyYW5kZScpCnRoZWFkcyA8LSBnZXRfYXJ0aXN0X2F1ZGlvX2ZlYXR1cmVzKCdUYWxraW5nIEhlYWRzJykKCiMgQ29tYmluZSBhbGwgb2YgdGhlIGFydGlzdHMnIGRhdGEKYWxsX2FydGlzdHMgPSByYmluZCh3aW5laG91c2UsIGJvd2llLCBnb3JpbGxheiwgZ3JhbmRlLCB0aGVhZHMpCgojIEZhY3RvciBhcnRpc3RfbmFtZSB0byB1c2UgaXQgZm9yIG91ciBHQk0hCmFsbF9hcnRpc3RzID0gYWxsX2FydGlzdHMgJT4lIAogIG11dGF0ZShhcnRpc3RfbmFtZSA9IGZhY3RvcihhcnRpc3RfbmFtZSkpCmBgYAoKTm93IHRoYXQgdGhlIGRhdGEgaXMgYWxsIHNldCwgd2UgY2FuIGZpdCBvdXIgR0JNIQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgMSBEYXRhIHNwbGl0dGluZwpzZXQuc2VlZCgxOCkgIyBpdCdzIGEgZ29vZCBudW1iZXIhIAphbGxfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGFsbF9hcnRpc3RzLCBwcm9wID0gMC43KQoKYWxsX3RyYWluID0gdHJhaW5pbmcoYWxsX3NwbGl0KQphbGxfdGVzdCA9IHRlc3RpbmcoYWxsX3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYWxsX2Jvb3N0ID0gCiAgYm9vc3RfdHJlZSgpICU+JQogIHNldF9tb2RlKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKSAlPiUKICBmaXQoYXJ0aXN0X25hbWUgfiBkYW5jZWFiaWxpdHkgKyBlbmVyZ3kgKyBsb3VkbmVzcyArIHNwZWVjaGluZXNzICsgYWNvdXN0aWNuZXNzICsgaW5zdHJ1bWVudGFsbmVzcyArIGxpdmVuZXNzICsgdGVtcG8gKyB2YWxlbmNlLCBkYXRhID0gYWxsX3RyYWluKQoKIyAzIEdlbmVyYXRlIHByZWRpY3Rpb25zCmFsbF9wcmVkID0gYWxsX2Jvb3N0ICU+JQogIHByZWRpY3QobmV3X2RhdGEgPSBhbGxfdGVzdCkKCiMgNCBQcmVkaWN0aW9uIG1ldHJpY3MKYWxsX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYWxsX3ByZWQkLnByZWRfY2xhc3MpICU+JQogIGFjY3VyYWN5KGVzdGltYXRlID0gYXJ0aXN0X3ByZWQsIHRydXRoID0gYXJ0aXN0X25hbWUpCgphbGxfdGVzdCAlPiUKICBtdXRhdGUoYXJ0aXN0X3ByZWQgPSBhbGxfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgpUaGUgYWNjdXJhY3kgaXMgKiowLjg0KiouIFRoYXQncyBub3QgYWxsIHRoYXQgYmFkLCBhY3R1YWxseSEgSW4gdGhpcyBjYXNlLCBzaW5jZSB3ZSBoYXZlIHNvIG1hbnkgbW9yZSB0YXJnZXRzIChhcnRpc3RzKSwgdGhlIGNvbmZ1c2lvbiBtYXRyaXggc3VpdHMgaXQgbmFtZSDigJQgaXQncyBwcmV0dHkgY29uZnVzaW5nLiBTdGlsbCwgSSBjYW4gc2VlIHRoYXQgQXJpYW5hIEdyYW5kZSBhbmQgRGF2aWQgQm93aWUgaGF2ZSB0aGUgaGlnaGVzdCBhY2N1cmFjeS4gSW4gKm9uZSBtb3JlIHRlc3QqLCBJIHdvbmRlciBob3cgYSBtb2RlbCB3b3VsZCBkbyBpZiBpdCBqdXN0IGhhZCB0byBsb29rIGF0IHRob3NlIHR3byBtdXNpY2lhbnM/CgojIyBCb251cyBJSTogSSBDYW4ndCBTdG9wIE1vZGVsaW5nIQoKTGV0J3MganVzdCBkbyB0aGlzIGFsbCBpbiBvbmUgZ286CgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDb21iaW5lIGFsbCBvZiB0aGUgYXJ0aXN0cycgZGF0YQpib3dpZV9ncmFuZGUgPSByYmluZChib3dpZSwgZ3JhbmRlKQoKIyBGYWN0b3IgYXJ0aXN0X25hbWUgdG8gdXNlIGl0IGZvciBvdXIgR0JNIQpib3dpZV9ncmFuZGUgPSBib3dpZV9ncmFuZGUgJT4lIAogIG11dGF0ZShhcnRpc3RfbmFtZSA9IGZhY3RvcihhcnRpc3RfbmFtZSkpCgojIDEgRGF0YSBzcGxpdHRpbmcKc2V0LnNlZWQoMTgpICMgaXQncyBhIGdvb2QgbnVtYmVyISAKYmdfc3BsaXQgPSBpbml0aWFsX3NwbGl0KGJvd2llX2dyYW5kZSwgcHJvcCA9IDAuNykKCmJnX3RyYWluID0gdHJhaW5pbmcoYmdfc3BsaXQpCmJnX3Rlc3QgPSB0ZXN0aW5nKGJnX3NwbGl0KQoKIyAyIEZpdCB0aGUgbW9kZWwKYmdfYm9vc3QgPSAKICBib29zdF90cmVlKCkgJT4lCiAgc2V0X21vZGUobW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIpICU+JQogIGZpdChhcnRpc3RfbmFtZSB+IGRhbmNlYWJpbGl0eSArIGVuZXJneSArIGxvdWRuZXNzICsgc3BlZWNoaW5lc3MgKyBhY291c3RpY25lc3MgKyBpbnN0cnVtZW50YWxuZXNzICsgbGl2ZW5lc3MgKyB0ZW1wbyArIHZhbGVuY2UsIGRhdGEgPSBiZ190cmFpbikKCiMgMyBHZW5lcmF0ZSBwcmVkaWN0aW9ucwpiZ19wcmVkID0gYmdfYm9vc3QgJT4lCiAgcHJlZGljdChuZXdfZGF0YSA9IGJnX3Rlc3QpCgojIDQgUHJlZGljdGlvbiBtZXRyaWNzCmJnX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYmdfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3koZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKCmJnX3Rlc3QgJT4lCiAgbXV0YXRlKGFydGlzdF9wcmVkID0gYmdfcHJlZCQucHJlZF9jbGFzcykgJT4lCiAgY29uZl9tYXQoZXN0aW1hdGUgPSBhcnRpc3RfcHJlZCwgdHJ1dGggPSBhcnRpc3RfbmFtZSkKYGBgCgoqKjAuOTggYWNjdXJhY3khISEqKiBUaGF0J3MgZGFybiBnb29kLCBlc3BlY2lhbGx5IHNpbmNlIHRoZSBjb25mdXNpb24gbWF0cml4IGlzbid0IHNrZXRjaHkgYWJvdXQgaXQhIAoKIyBDb25jbHVzaW9uCgpUaHJvdWdob3V0IHRoaXMgZmluYWwgcHJvamVjdCwgYSBsb3Qgb2YgY29vbCBzdGF0cyB3ZXJlIGFuYWx5emVkLiBJIGxlYXJuZWQgYWJvdXQgV2luZWhvdXNlLCBCb3dpZSwgYW5kIGhvdyBkaXN0aW5jdCBzb21lIG9mIHRoZSBtdXNpYyB0aGF0IEkgbGlzdGVuIHRvIGNhbiBiZSDigJQgRGF2aWQgQm93aWUgYW5kIEFyaWFuYSBHcmFuZGUgaW4gcGFydGljdWxhci4KCkkgaG9wZSB0aGlzIGRhdGEgYW5hbHlzaXMgd2FzIGludGVyZXN0aW5nIGFuZCBJIGVuY291cmFnZSB5b3UgdG8gYW5hbHl6ZSB5b3VyIG93biBTcG90aWZ5IGRhdGEhIFRoYW5rIHlvdSBmb3IgcmVhZGluZyB0aGlzIHBvc3QgYW5kIEkgaG9wZSB5b3UgaGF2ZSBhIGdyZWF0IGRheSEKCiFbXShodHRwczovL2NoYWJlY2hyb2QuZmlsZXMud29yZHByZXNzLmNvbS8yMDE1LzA3L3R1bWJscl9ub281bjl4dXB0MXIzcHRiZm8xXzU0MC5naWY/dz01NDAmem9vbT0yKQo=